package com.spzx.channel.service.impl;

import com.alibaba.fastjson2.JSON;
import com.spzx.channel.domain.SpecValue;
import com.spzx.channel.service.IndexService;
import com.spzx.channel.vo.IndexVo;
import com.spzx.channel.vo.ProductInfoVo;
import com.spzx.channel.vo.SkuPrice;
import com.spzx.channel.vo.SkuStockVo;
import com.spzx.common.core.constant.SecurityConstants;
import com.spzx.common.core.domain.R;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.StringUtils;
import com.spzx.common.core.utils.uuid.UUID;
import com.spzx.common.redis.service.RedisService;
import com.spzx.product.api.RemoteBrandService;
import com.spzx.product.api.RemoteCategoryService;
import com.spzx.product.api.RemoteProductSkuService;
import com.spzx.product.api.domain.dto.*;
import com.spzx.product.api.domain.param.SkuQuery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class IndexServiceImpl implements IndexService {

    private static final String PRODUCT_DETAIL_INFO = "channel:product:item:";

    private static final String PRODUCT_DETAIL_LOCK = "channel:product:lock:";

    // 定义ThreadLocal，并提供初始值
    private static final ThreadLocal<Integer> retryTime = ThreadLocal.withInitial(() -> 0);

    @Autowired
    private RemoteCategoryService categoryService;

    @Autowired
    private RemoteProductSkuService productSkuService;

    @Autowired
    private RemoteBrandService brandService;

    @Autowired
    private ThreadPoolExecutor threadPool;

    @Autowired
    private RedisService redisService;

    @Autowired
    public RedisTemplate redisTemplate;

    @Override
    public IndexVo getIndexData() {
        IndexVo vo = new IndexVo();
        CompletableFuture<Void> categoryFuture = CompletableFuture.runAsync(() -> {
            // 查询商品一级分类数据
            R<List<CategoryDto>> result = categoryService.getCategoryLevel1(SecurityConstants.INNER);
            if (StringUtils.isNull(result) || StringUtils.isNull(result.getData())) {
                throw new ServiceException("系统繁忙，请稍后再试");
            }
            if (R.FAIL == result.getCode()) {
                throw new ServiceException("商品一级分类数据查询失败");
            }
            vo.setCategoryList(result.getData());
        }, threadPool);
        // 查询畅销商品列表
        CompletableFuture<Void> productSkuFuture = CompletableFuture.runAsync(() -> {
            R<List<ProductSkuDto>> result = productSkuService.findProductSkuNo20(SecurityConstants.INNER);
            if (StringUtils.isNull(result) || StringUtils.isNull(result.getData())) {
                throw new ServiceException("系统繁忙，请稍后再试");
            }
            if (R.FAIL == result.getCode()) {
                throw new ServiceException("畅销商品列表查询失败");
            }
            vo.setProductSkuList(result.getData());
        }, threadPool);
        CompletableFuture.allOf(categoryFuture, productSkuFuture).join();
        return vo;
    }

    // todo:此处可以考虑在当前service的方法上加缓存管理，product服务的远程方法只负责查库和数据封装
    @Override
    public List<CategoryDto> categoryTree() {
        R<List<CategoryDto>> result = categoryService.allTree(SecurityConstants.INNER);
        if (StringUtils.isNull(result) || StringUtils.isNull(result.getData())) {
            throw new ServiceException("系统繁忙，请稍后再试");
        }
        if (R.FAIL == result.getCode()) {
            throw new ServiceException("查询商品分类整体树形结构失败");
        }
        return result.getData();
    }

    @Override
    public List<BrandDto> getBrandAll() {
        R<List<BrandDto>> result = brandService.getBrandAll(SecurityConstants.INNER);
        if (StringUtils.isNull(result) || StringUtils.isNull(result.getData())) {
            throw new ServiceException("系统繁忙，请稍后重试");
        }
        if (R.FAIL == result.getCode()) {
            throw new ServiceException("查询品牌集合失败");
        }
        return result.getData();
    }

    @Override
    public List<ProductSkuDto> skuList(Integer pageNum, Integer pageSize, SkuQuery query) {
        R<List<ProductSkuDto>> result = productSkuService.skuList(pageNum, pageSize, query, SecurityConstants.INNER);
        if (StringUtils.isNull(result) || StringUtils.isNull(result.getData())) {
            throw new ServiceException("系统繁忙，请稍后再试");
        }
        if (R.FAIL == result.getCode()) {
            throw new ServiceException("条件查询商品集合失败");
        }
        return result.getData();
    }

    @Override
    public ProductInfoVo getItemBySkuId(Long skuId) {
        if (retryTime.get() == 3) {
            throw new ServiceException("系统繁忙，请稍后再试");
        }
        ProductInfoVo productInfoVo = null;
        // 先从缓存中获取商品信息详情
        try {
            String productKey = PRODUCT_DETAIL_INFO + skuId;
            String cacheItem = (String) redisTemplate.opsForValue().get(productKey);
            if (StringUtils.isNotEmpty(cacheItem)) {
                productInfoVo = JSON.parseObject(cacheItem, ProductInfoVo.class);
                return productInfoVo;
            }
            // 如果缓存命中，直接返回结果，返回不命中，先尝试获取分布式锁
            String redisLock = PRODUCT_DETAIL_LOCK + skuId;
            String lockValue = UUID.randomUUID().toString().replace("-", "");
            // 如果未获取到锁，则等待一段时间后重试获取锁，若可以获取到锁，则执行远程调用
            Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(redisLock, lockValue, 10, TimeUnit.SECONDS);
            if (lockResult) {
                try {
                    productInfoVo = getItemFormProductService(skuId);
                    long ttl = (productInfoVo == null) ? 60 : 3600;
                    // 封装结果后，将结果重新缓存到redis中，然后释放锁\
                    redisTemplate.opsForValue().set(PRODUCT_DETAIL_INFO + skuId,
                            JSON.toJSONString(productInfoVo),
                            ttl,
                            TimeUnit.SECONDS);
                } finally {
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                    String releaseLockScript = "if redis.call(\"get\", KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "    return redis.call(\"del\", KEYS[1])\n" +
                            "else\n" +
                            "    return 0\n" +
                            "end";
                    redisScript.setScriptText(releaseLockScript);
                    redisScript.setResultType(Long.class);
                    redisTemplate.execute(redisScript, Arrays.asList(redisLock), lockValue);
                }
            } else {
                try {
                    Thread.sleep(100);
                    retryTime.set(retryTime.get() + 1);
                    productInfoVo = this.getItemBySkuId(skuId);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            //兜底处理方案：Redis服务有问题，将业务数据获取自动从数据库获取
            log.error("[商品服务]查询商品信息异常：{}", e);
            productInfoVo = this.getItemFormProductService(skuId);
        }
        // 最后返回结果
        retryTime.remove();
        return productInfoVo;
    }

    private ProductInfoVo getItemFormProductService(Long skuId) {
        ProductInfoVo infoVo = new ProductInfoVo();
        R<ProductInfoDto> resultData = productSkuService.getProductInfoBySkuId(skuId, SecurityConstants.INNER);
        if (StringUtils.isNull(resultData) || StringUtils.isNull(resultData.getData())) {
            throw new ServiceException("查询商品详情信息失败");
        }
        if (R.FAIL == resultData.getCode()) {
            throw new ServiceException(resultData.getMsg());
        }
        ProductInfoDto data = resultData.getData();
        ProductSkuDto productSku = data.getProductSku();
        ProductDto product = data.getProductVo();
        infoVo.setProductSku(productSku);
        infoVo.setProduct(product);
        // skuPrice
        SkuPrice skuPrice = SkuPrice.builder()
                .skuId(skuId)
                .salePrice(productSku.getSalePrice())
                .marketPrice(productSku.getMarketPrice()).build();
        infoVo.setSkuPrice(skuPrice);
        // sliderUrlList
        List<String> sliderUrlList = Arrays.asList(product.getSliderUrls().split(","));
        infoVo.setSliderUrlList(sliderUrlList);
        // detailsImageUrlList
        infoVo.setDetailsImageUrlList(product.getDetailsImageUrlList());
        // specValueList
        List<SpecValue> specValueList = JSON.parseArray(data.getSpecValueList(), SpecValue.class);
        infoVo.setSpecValueList(specValueList);
        // skuStockVo
        SkuStockVo skuStockVo = SkuStockVo.builder()
                .skuId(skuId)
                .availableNum(productSku.getStockNum())
                .saleNum(productSku.getSaleNum()).build();
        infoVo.setSkuStockVo(skuStockVo);
        // skuSpecValueMap
        List<ProductSkuDto> skuList = product.getProductSkuList();
        Map<String, Long> skuSpecValueMap = skuList.stream().collect(
                Collectors.toMap(ProductSkuDto::getSkuSpec, ProductSkuDto::getId));
        infoVo.setSkuSpecValueMap(skuSpecValueMap);
        return infoVo;
    }
}
